package biolabs.model

import biolabs.core.BioContext
import biolabs.core.BioObject
import biolabs.core.OperationFailedException
import biolabs.core.WrappedDelegate
import biolabs.model.abscretion.Abscretion
import biolabs.model.abscretion.Abscretor
import biolabs.model.abscretion.SubstanceHolder
import biolabs.model.diffusion.Diffusion
import biolabs.model.division.Divisible
import biolabs.model.division.Division
import biolabs.model.growth.Growable
import biolabs.model.growth.Growth
import biolabs.model.motion.Motion
import biolabs.model.motion.Movable
import biolabs.model.react.Reaction
import biolabs.model.react.ReactionKinetics
import biolabs.space.ContinuousPosition
import biolabs.space.DiscretePosition
import biolabs.space.GenericGrid
import biolabs.space.Grid
import biolabs.space.Grids
import biolabs.space.GridShapedObject
import biolabs.util.Concurrency
import biolabs.util.Pair

import java.util.HashMap
import java.util.LinkedList

import scala.collection.JavaConversions._

object LiquidEnvironment {

	type ChildType[P <: DiscretePosition[P, C], C <: ContinuousPosition[C, P], Self <: Movable[Self, P] with Abscretor[Self, P]] =
		BioObject
		with GridShapedObject[P, C]
		with Divisible[Self, P]
		with Growable[Self, P]
		with Movable[Self, P]
		with Abscretor[Self, P]
}

/**
 * BioObject that mimics the behavior of a liquid environment.
 * It performs diffusion of contained substances and supports
 * division, growth and motion of contained bio-objects and
 * secretion/absorption of substances by contained bio-objects.
 *
 * @param <P> type of discrete vectors that determine
 *   the space representation of this environment
 * @param <C> continuous analog of {@code P}
 * @param <T> type of contained bio-objects
 *
 * @author Tomas Mikula
 */
abstract class LiquidEnvironment[
	P <: DiscretePosition[P, C],
	C <: ContinuousPosition[C, P],
	T <: LiquidEnvironment.ChildType[P, C, T]
](
	dimensions: P,
	diffCoeff: Double,
	substances: Array[Any],
	reactions: Iterable[Reaction]
)
extends BioObject
with SubstanceHolder[P, C]
with GridBioContainer[T, P, C]
{
	private final val diffusion = new Diffusion[P, C](diffCoeff)
	private final val abscretion = new Abscretion[T, P, C]
	private final val division = new Division[T, P, C]
	private final val growth = new Growth[T, P, C]
	private final val motion = new Motion[T, P, C]
	private final val reactionKinetics = new ReactionKinetics(reactions)

	override protected val concentrations = new HashMap[Any, Grid[java.lang.Double, P, C]](substances.size)
	for(s <- substances) {
		val conc = new GenericGrid[java.lang.Double, P, C](dimensions);
		conc.fill(0d);
		concentrations.put(s, conc);
	}

	private val childMask = new GenericGrid[T, P, C](dimensions)
	private final val children = new LinkedList[T]();

	override def getChildMask = childMask
	override def isValidPos(p: P) = childMask.inRange(p)
	override def getChildAt(p: P) = childMask.at(p)
	override def setChildAt(p: P, child: T) = childMask.setField(p, child)
	override def getConcentrationMask = Grids.asBooleanGrid(childMask, Grids.IsNull)

	override def getChildren() = children
	override def getChild(i: Int) = children.get(i)
	override def getChildCount() = children.size

	@throws(classOf[OperationFailedException])
	override def addChild(child: T) {

		// check if the position for the child is free
		for(p <- child.getShape().getElements()) {
			if(!childMask.inRange(p))
				throw new OperationFailedException("The new child" +
						" would exceed the boundaries of the environment");
			if(childMask.at(p) != null)
				throw new OperationFailedException(
						"The space for the new child is already occupied");
		}

		addChild(child, child.getShape().getElements())
	}

	override def addChild(child: T, space: Iterable[P]) {
		// assign the space for child
		for(p <- space)
			childMask.setField(p, child);

		// include child in children
		children.add(child);
	}

	override def notifyCapture(pos: P) = {
		val l = new java.util.LinkedList[P]
		l.add(pos)
		notifyCapture(l)
	}

	override def notifyCapture(captured: java.util.Collection[P]) = {
		super[SubstanceHolder].handleCapture(captured)
	}

	override def proceed(startTime: Long, endTime: Long, ctx: BioContext): Unit = {
		for(time <- startTime until endTime) {

			Concurrency.proceed(getChildren(), time, time+1, ctx)

			diffusion.perform(this, (Unit)=>())

			reactionKinetics.perform(this, (Unit)=>())

			for(s <- substances) {
				abscretion.perform(this, getChildren map {_.abscretionDelegate_wrapped(s)} filter {_.isDefined} map {_.get}, (u:Unit)=>())
			}

			growth.perform(this, getChildren map {_.growthDelegate_wrapped()} filter {_.isDefined} map {_.get}, (u:Unit)=>())

			motion.perform(this, getChildren map {_.motionDelegate_wrapped()} filter {_.isDefined} map {_.get}, (Unit)=>())

			division.perform(this, getChildren map {_.divisionDelegate_wrapped()} filter {_.isDefined} map {_.get}, (Unit)=>())
		}
	}

	override def close() {
		// do nothing
	}
}